package com.google.code.multitester.annonations.processors;

import com.google.code.multitester.annonations.Exported;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Helper for picking out annotated methods.
 * <p>
 * This class goes through given instance, finds all methods annotated with
 * {@link com.google.code.multitester.annonations.Exported} and collects them
 * into bunch of pointers represented by
 * {@link com.google.code.multitester.annonations.processors.VoidMethodPointer}.
 * Also finder performs some validations and exception handling.
 *
 * @author Yuriy Sechko
 */
public class ExportedMethodsFinder
{
	private final Object instance;

	/**
	 * Creates finder for given instance.
	 * 
	 * @param instance
	 *            object for search in
	 * 
	 * @throws java.lang.IllegalArgumentException
	 *             when given instance is null
	 */
	public ExportedMethodsFinder(final Object instance)
	{
		Checkings.instanceIsNull(instance);
		this.instance = instance;
	}

	private Map<String, VoidMethodPointer> findInClass(final Class clazz,
			final Map<String, VoidMethodPointer> exportedMethodsAlreadyFound)
	{
		final Map<String, VoidMethodPointer> exportedMethods = new HashMap<String, VoidMethodPointer>();
		for (final Method currentMethod : clazz.getDeclaredMethods())
		{
			final Annotation demandedAnnotation = currentMethod
					.getAnnotation(Exported.class);
			if (demandedAnnotation == null)
				continue;
			final Exported actualAnnotation = (Exported) demandedAnnotation;
			final String alias = actualAnnotation.value();
			Checkings.isAliasPresentedInMaps(alias, exportedMethods,
					exportedMethodsAlreadyFound);
			exportedMethods.put(alias, new VoidMethodPointer(instance,
					currentMethod));
		}
		exportedMethods.putAll(exportedMethodsAlreadyFound);
		if (clazz.getSuperclass() != null)
		{
			exportedMethods.putAll(findInClass(clazz.getSuperclass(),
					exportedMethods));
		}
		return exportedMethods;
	}

	/**
	 * Goes through the specified class and all of its superclasses and finds
	 * all methods annotated with
	 * {@link com.google.code.multitester.annonations.Exported} annotation,
	 * performs needful validations and collects pointers into
	 * {@link java.util.Map}.
	 * <p>
	 * Only 'void-methods' can be picked out. Also checking for duplicated
	 * aliases is performed. For example if some method marked as
	 * {@link com.google.code.multitester.annonations.Exported} with particular
	 * alias in given class and the same alias used for another
	 * {@link com.google.code.multitester.annonations.Exported} method then
	 * error will rise.
	 * 
	 * @throws java.lang.IllegalAccessException
	 *             when duplicated alias encountered in the hierarchy starting
	 *             from given instance's class
	 * 
	 * @throws java.lang.IllegalArgumentException
	 *             when non 'void-method' encountered
	 * 
	 * @return map of the aliases and corresponding methods pointers. If there
	 *         are not methods found empty map will be returned
	 */
	public Map<String, VoidMethodPointer> find()
	{
		final Map<String, VoidMethodPointer> alreadyFoundExported = new HashMap<String, VoidMethodPointer>();
		return findInClass(instance.getClass(), alreadyFoundExported);
	}

	private static class Checkings
	{
		public static void isAliasPresentedInMaps(final String alias,
				final Map<String, VoidMethodPointer> exportedMethods,
				final Map<String, VoidMethodPointer> exportedMethodsAlreadyFound)
		{
			if (exportedMethods.get(alias) != null
					|| exportedMethodsAlreadyFound.get(alias) != null)
				throw new IllegalStateException(
						"Some aliases of exported methods are duplicated.");
		}

		public static void instanceIsNull(Object instance)
		{
			if (instance == null)
				throw new IllegalArgumentException(
						"Input object should not be null.");
		}
	}
}
